﻿/*
 * OpenDoPE Word Add-In
 * Copyright (C) 2010-2011  Plutext Pty Ltd
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * For details, see http://www.gnu.org/licenses/gpl-2.0.html
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Integration;

using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;

using ICSharpCode.Core;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.AvalonEdit.AddIn;
using ICSharpCode.SharpDevelop.Sda; // startup

using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Reflection;

using ICSharpCode.XmlEditor;

using NLog;

using Office = Microsoft.Office.Core;

/*
 * The referenced assembly "ICSharpCode.AvalonEdit.AddIn" could not be 
 * resolved because it has a dependency on "Microsoft.Build.Framework, 
 * Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
 * which is not in the currently targeted framework 
 * ".NETFramework,Version=v4.0,Profile=Client". 
 * 
 * Please remove references to assemblies not in the targeted framework 
 * or consider retargeting your project.
 */

namespace OpenDoPE_Wed
{
    public partial class XmlEditorControl : UserControl
    {
        static Logger log = LogManager.GetLogger("OpenDoPE_Wed");

        static XmlEditorControl()
        {

            //Assembly exe = typeof(XmlEditorControl).Assembly;
            //string path = exe.Location;
            //log.Info(".LOcation --> " + path);

            string dir = Assembly.GetExecutingAssembly().CodeBase;
            dir = dir.Replace(@"file:///", string.Empty);
            int lastPost = dir.LastIndexOf("/");
            dir = dir.Substring(0, lastPost);// +"/help/";
            dir = dir.Replace("/", "\\");
            log.Debug("dir " + dir);


            /*
             * .LOcation --> C:\Users\jharrop\AppData\Local\assembly\dl3\OY0M6YPE.QWZ\89CALXQ4.492\75bdc13b\294aa609_d568cb01\OpenDoPE_Wed.DLL
             * 
             * but this only contains that dll.

             *  file:///C:/Users/jharrop/Documents/Visual Studio 2010/Projects/OpenDope_Wed/OpenDoPE_Wed/bin/Debug/OpenDoPE_Wed.DLL
             */

            // First, set up, more or less as per SharpDevelopMain.cs 

            ICSharpCode.SharpDevelop.Sda.StartupSettings settings =
                new ICSharpCode.SharpDevelop.Sda.StartupSettings();
            //settings.ApplicationRootPath = @"C:\Users\jharrop\Documents\Visual Studio 2010\Projects\OpenDope_Wed\OpenDoPE_Wed\bin\Debug";
            settings.ApplicationRootPath = dir;
            settings.ResourceAssemblyName = "ICSharpCode.SharpDevelop";
            //settings.AddAddInsFromDirectory(@"C:\Users\jharrop\Documents\Visual Studio 2010\Projects\OpenDope_Wed\OpenDoPE_Wed\bin\Debug");

            //settings.ConfigDirectory = dir + @"\sdconfig\"; // schema live in here

            // Make sure you have this, or you'll get all sorts of weird errors
            // for example, you end up needing to do stuff like
            //    AddInTree.Doozers.Add("DisplayBinding", new DisplayBindingDoozer());
//            settings.AddAddInFile(@"C:\Users\jharrop\Documents\Visual Studio 2010\Projects\OpenDope_Wed\OpenDoPE_Wed\bin\Debug\ICSharpCode.SharpDevelop.addin");

            // Now the ones we really want ...
//            settings.AddAddInFile(@"C:\Users\jharrop\Documents\Visual Studio 2010\Projects\OpenDope_Wed\OpenDoPE_Wed\bin\Debug\AvalonEdit.AddIn.addin");
//            settings.AddAddInFile(@"C:\Users\jharrop\Documents\Visual Studio 2010\Projects\OpenDope_Wed\OpenDoPE_Wed\bin\Debug\XmlEditor.addin");

            settings.AddAddInFile(dir + @"\ICSharpCode.SharpDevelop.addin");

            // Now the ones we really want ...
            settings.AddAddInFile(dir + @"\AvalonEdit.AddIn.addin");
            settings.AddAddInFile(dir + @"\XmlEditor.addin");
            /*
             * Would have liked to have put them in a sub-dir together, but
             * the actual dll is looked for in the same dir as the .addin file,
             * so its easiest just to put them in the root dir
             */ 

            /* To avoid:
             *
             * System.Resources.MissingManifestResourceException occurred
  Message=Could not find any resources appropriate for the specified culture or the neutral culture.  
  Make sure "Resources.StringResources.resources" was correctly embedded or linked into assembly "ICSharpCode.SharpDevelop" at compile time, 
  or that all the satellite assemblies required are loadable and fully signed.

             * 
             * I copied the resources into ICSharpCode.SharpDevelop, re-built it, and used
             * the resulting dll in place of the one in the binary distribution.
             * 
             * Also had to create BitmapResources.resources using resasm
             * 
             */
            //settings.DataDirectory=@"C:\Users\jharrop\Documents\Visual Studio 2010\Projects\OpenDope_Wed\OpenDoPE_Wed\bin\Debug";

            ICSharpCode.SharpDevelop.Sda.SharpDevelopHost sharpHost =
                new ICSharpCode.SharpDevelop.Sda.SharpDevelopHost(AppDomain.CurrentDomain,
                     settings); // Needs log4net

            /* Second, need WorkbenchSingleton.Workbench to be non-null, or there are 25 changes
             * 
             * But don't do:
             * 
                ICSharpCode.SharpDevelop.Sda.WorkbenchSettings wbs =
                    new ICSharpCode.SharpDevelop.Sda.WorkbenchSettings();
                wbs.RunOnNewThread = false;
                sharpHost.RunWorkbench(wbs);
             * 
             * because that gives us the whole UI, and takes control of input.
             * 
             * Instead:
            */

            ICSharpCode.SharpDevelop.Commands.WorkbenchStartup workbenchStartup
                = new ICSharpCode.SharpDevelop.Commands.WorkbenchStartup();
            workbenchStartup.InitializeWorkbench(); // Needs AvalonDock dll
            // Uses ResourceService

        }

        private Office.CustomXMLPart cxp;

        private System.Windows.Forms.TabControl tc;
        
        private System.Windows.Forms.TabPage tabEdit;
        private AvalonEditViewContent editView;

        private System.Windows.Forms.TabPage tabTree;
        private XmlTreeView treeView;

        private OpenedFile openedFile;

        public System.Windows.Forms.TextBox xpathTextBox   { get; set; }
        public System.Windows.Forms.TextBox textBoxXPathID { get; set; }

        public XmlEditorControl(Office.CustomXMLPart cxp, string ext, bool displayXPath, int width)
        {
            this.cxp = cxp;
            Office.CustomXMLNode node = cxp.SelectSingleNode("/node()");
            setup(System.Text.Encoding.UTF8.GetBytes(node.XML), 
                ext, displayXPath, width, null);

        }

        public XmlEditorControl(byte[] data, string ext, bool displayXPath, int width)
        {
            setup(data, ext, displayXPath, width, Encoding.Unicode);
        }

        public void setup(byte[] data, string ext, bool displayXPath, int width, Encoding enc)
        {
            InitializeComponent();

            openedFile = new MyOpenedFile();

            openedFile.SetData(data);
            openedFile.FileName = new FileName("foo" + ext);



            editView = //binding.CreateContentForFile(file);
                new AvalonEditViewContent(openedFile, enc);

            // Highlighting is provided via my SyntaxMode 
            // definition in AvalonEdit.AddIn.addin

            log.Info("editView setup :-) ");

            XmlSchemaFileAssociations assocs = XmlEditorService.XmlSchemaFileAssociations;
            assocs.SetSchemaFileAssociation(
                new XmlSchemaFileAssociation(".xpaths", "http://opendope.org/xpaths")
                );
            assocs.SetSchemaFileAssociation(
                new XmlSchemaFileAssociation(".con", "http://opendope.org/conditions")
                );

            log.Info("Set up assocs");

            XmlSchemaCompletion defaultSchema = assocs.GetSchemaCompletion(
                editView.PrimaryFileName);

            //log.Info("Got default schema for " + editView.PrimaryFileName + " = " + defaultSchema.NamespaceUri);

            RegisteredXmlSchemas registeredSchemas = XmlEditorService.RegisteredXmlSchemas;

            log.Info("Got registeredSchemas");

            treeView = new XmlTreeView(editView,
                registeredSchemas.Schemas, defaultSchema);
            editView.SecondaryViewContents.Add(treeView); // ?
            ((XmlTreeView)treeView).MyLoadFromPrimary();
            

            log.Info("treeView setup :-) ");

            // Create the ElementHost control for hosting the
            // AvalonEdit WPF UserControl.
            ElementHost host = new ElementHost();
            host.Dock = DockStyle.Fill;

            tc = new TabControl();
            //tc.Dock = DockStyle.Fill;
            tc.Size = new System.Drawing.Size(width, 300); // 300 high

            tabEdit = new TabPage();
            tabEdit.Size = new System.Drawing.Size(width, 290);

            // Assign the WPF UserControl to the ElementHost control's
            // Child property.
            host.Child = (System.Windows.UIElement)editView.Control;

            tabEdit.Controls.Add(host);
            tc.Controls.Add(tabEdit);

            tabTree = new TabPage();
            tabTree.Size = new System.Drawing.Size(width, 290); // 290 high
            tabTree.Controls.Add((UserControl)treeView.Control);

            // Size the treeView control
            XmlTreeViewContainerControl xmlTreeViewContainerControl 
                = (XmlTreeViewContainerControl)treeView.Control;
            xmlTreeViewContainerControl.setWidth(width);

            tc.Controls.Add(tabTree);

            tc.Selecting += new System.Windows.Forms.TabControlCancelEventHandler(this.tc_Selecting);
            // See ICSharpCode.SharpDevelop.Gui.AvalonWorkBenchWindow for
            // how they manage theirs 

            this.Controls.Add(tc);

            // XPath box - only for bind / repeat, not for conditions
            if (displayXPath)
            {
                xpathTextBox = new TextBox();
                xpathTextBox.Location = new Point(10, 310);
                xpathTextBox.Size = new System.Drawing.Size(width, 30); //was 250
                this.Controls.Add(xpathTextBox);

                Label label2 = new Label();
                label2.AutoSize = true;
                label2.Location = new System.Drawing.Point(3, 350);
                label2.Name = "label2";
                label2.Size = new System.Drawing.Size(53, 13);
                label2.Text = "XPath ID:";
                this.Controls.Add(label2);

                textBoxXPathID = new TextBox();
                this.textBoxXPathID.Location = new System.Drawing.Point(77, 350);
                this.textBoxXPathID.Name = "textBoxXPathID";
                this.textBoxXPathID.Size = new System.Drawing.Size(141, 20);
                this.Controls.Add(textBoxXPathID);

                editView.TextEditor.Caret.PositionChanged += new EventHandler(Caret_PositionChanged);

                ((XmlTreeViewContainerControl)treeView.Control).TreeView.AfterSelect += new TreeViewEventHandler(XmlEditorControl_AfterSelect);
            }
        }

        /// <summary>
        /// Display path for tree view
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void XmlEditorControl_AfterSelect(object sender, TreeViewEventArgs e)
        {
            XmlTreeEditor xte = ((XmlTreeViewContainerControl)treeView.Control).getXmlTreeEditor();
                
            // Just in case our event is the first to fire
            xte.SelectedNodeChanged();

            XmlElementPath xep = XmlTreeEditor.GetElementPath(
                xte.MyGetSelectedElement() );

            xpathTextBox.Text = ToXpathString(xep.Elements);

        }

        /// <summary>
        /// Display path for text edit view
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Caret_PositionChanged(object sender, EventArgs e)
        {
            SelectedXmlElement sex = new SelectedXmlElement(editView.TextEditor);
            if (sex.HasSelectedAttribute)
            {
                xpathTextBox.Text = ToXpathString(sex.Path.Elements) + "/@" + sex.SelectedAttribute;
            }
            else
            {
                xpathTextBox.Text = ToXpathString(sex.Path.Elements);
            }
        }

        string ToXpathString(QualifiedNameCollection qnc)
        {
            StringBuilder text = new StringBuilder();
            for (int i = 0; i < qnc.Count; i++)
            {
                text.Append("/");
                text.Append(qnc[i].ToString());
            }
            return text.ToString();
        }


        private void tc_Selecting(object sender, TabControlCancelEventArgs e)
        {
            log.Debug("Switching view..");

            // Save in the first view
            try
            {
                if (this.tc.SelectedTab == tabEdit)
                {
                    // so last tab was tree
                    treeView.MySaveToPrimary();
                        // TODO .. explicitly catch errors here,
                        // or is that method enough?
                }

                //xmlTextEditor1.CommitChanges();

                // CXP ReplaceChildSubtree seems to kill Event handlers dead?
                // .. so re-create them,
                //setupCcEvents(Globals.ThisAddIn.Application.ActiveDocument);

            }
            catch (Exception ex)
            {
                OpenDoPE_Wed.Mbox.ShowSimpleMsgBoxWarning(OpenDoPE_Wed.Properties.Resources.FixXmlErrorRequestBeforeSwitchViews +
                                             Environment.NewLine + Environment.NewLine + "\t" + ex.Message.ToString());
                e.Cancel = true;
                return;
            }
            log.Debug(".. old view saved");


            // Initialise the other 
            if (this.tc.SelectedTab == tabEdit)
            {
                //editView.Load(openedFile, ms);
            }
            else
            {

                if (!isWellFormed(editView.TextEditor.Document.Text))
                {
                    e.Cancel = true;
                    return;
                }

                treeView.MyLoadFromPrimary();
            }
            log.Debug(".. new view loaded");

            commitChanges();              
        }

        public bool isWellFormed() {

            return isWellFormed(getXml());
        }
        public bool isWellFormed(string xml)
        {
            // Check it is well formed
            try
            {
                System.Xml.XmlDocument Document = new System.Xml.XmlDocument();
                Document.LoadXml(xml);
            }
            catch (System.Xml.XmlException ex)
            {
                OpenDoPE_Wed.Mbox.ShowSimpleMsgBoxWarning(OpenDoPE_Wed.Properties.Resources.FixXmlErrorRequestBeforeSwitchViews +
                                             Environment.NewLine + Environment.NewLine + "\t" + ex.Message.ToString()
                                             + " line " + (ex.LineNumber - 1) + ", col " + (ex.LinePosition - 1));
                return false;
            }
            catch (Exception ex)
            {
                OpenDoPE_Wed.Mbox.ShowSimpleMsgBoxWarning(OpenDoPE_Wed.Properties.Resources.FixXmlErrorRequestBeforeSwitchViews +
                                             Environment.NewLine + Environment.NewLine + "\t" + ex.Message.ToString());
                return false;
            }
            return true;
        }

        public bool isDirty()
        {
            if (this.tc.SelectedTab == tabTree)
            {
                log.Debug("in tree view.  is dirty? " + treeView.IsDirty);
                return treeView.IsDirty;
            }
            else
            {
                log.Debug("in edit view.  is dirty? " + editView.IsDirty);
                return editView.IsDirty;
            }
        }

        public void commitChanges()
        {

            if (this.tc.SelectedTab == tabTree)
            {
                // Assume its well formed in that view.
                treeView.MySaveToPrimary();
            }

            // ok, can now save from primary view
            ICSharpCode.SharpDevelop.Editor.IFileDocumentProvider provider
                = treeView.PrimaryViewContent as ICSharpCode.SharpDevelop.Editor.IFileDocumentProvider;
            string result = provider.GetDocumentForFile(treeView.PrimaryFile).Text;

            //string result = editView.TextEditor.Document.Text;

            if (cxp != null) // ie unless we're looking at document.xml
            {
                log.Debug("Saving " + result);
                CustomXmlUtilities.replaceXmlDoc(cxp, result);
                log.Debug(".. saved to part");
            }

        }

        public string getXml()
        {
            if (this.tc.SelectedTab == tabEdit)
            {
                return editView.TextEditor.Document.Text;
            }
            else
            {
                return 
                    ((XmlTreeViewContainerControl)treeView.Control).Document.OuterXml;
            }

        }

        public string getPrefixMappings()
        {
            // TODO .. simple minded.  Only returns prefix of document itself.
            try
            {
                System.Xml.XmlDocument Document = new System.Xml.XmlDocument();
                Document.LoadXml(getXml() );

                log.Info("document element --> " + Document.DocumentElement.Name);
/*                string prefix = Document.DocumentElement.Prefix;
                string ns = Document.DocumentElement.GetNamespaceOfPrefix(prefix);

                string prefixMapping = "xmlns:" + prefix + "='" + ns + "' ";
                log.Info("prefix mapping --> " + prefixMapping);
                */

                string prefixMapping="";
                foreach(System.Xml.XmlAttribute a in Document.DocumentElement.Attributes) {
                    log.Debug(a.Name);
                    if (a.Name.StartsWith("xmlns:")) {
                        prefixMapping = prefixMapping + a.Name + "='" + a.Value + "' ";
                    } else if (a.Name.StartsWith("xmlns")) {
                        //prefixMapping = prefixMapping + a.Name + "='" + a.Value + "' ";
                        OpenDoPE_Wed.Mbox.ShowSimpleMsgBoxWarning("Discarding default namespace, since Word can't use it. Where namespaces are used, all element names must use an explicit namespace prefix.");

                        // including eg xmlns=.. (cf xmlns:ns1=) doesn't cause an error
                        // but Word ignores it.
                        // TODO: auto add prefix nsx declaration, and to each segment in xpath?
                    }
                }
                log.Info("prefix mapping --> " + prefixMapping);

                return prefixMapping;
            }
            catch (System.Xml.XmlException ex)
            {
                OpenDoPE_Wed.Mbox.ShowSimpleMsgBoxWarning(OpenDoPE_Wed.Properties.Resources.FixXmlErrorRequestBeforeSwitchViews +
                                             Environment.NewLine + Environment.NewLine + "\t" + ex.Message.ToString()
                                             + " line " + (ex.LineNumber - 1) + ", col " + (ex.LinePosition - 1));
            }
            catch (Exception ex)
            {
                OpenDoPE_Wed.Mbox.ShowSimpleMsgBoxWarning(OpenDoPE_Wed.Properties.Resources.FixXmlErrorRequestBeforeSwitchViews +
                                             Environment.NewLine + Environment.NewLine + "\t" + ex.Message.ToString());
            }
            return null;
        }

        /// <summary>
        /// Check that the user has entered something in the
        /// XPath text box and for the XPath ID.
        /// Check the XPath ID is an NCName.
        /// </summary>
        /// <returns></returns>
        public bool isInputOK()
        {
            if (string.IsNullOrWhiteSpace(xpathTextBox.Text))
            {
                Mbox.ShowSimpleMsgBoxError("You need to enter a valid XPath");
                return false;
            }
            if (string.IsNullOrWhiteSpace(textBoxXPathID.Text))
            {
                Mbox.ShowSimpleMsgBoxError("You need to choose an ID for the XPath");
                return false;
            }
            try
            {
                System.Xml.XmlConvert.VerifyNCName(textBoxXPathID.Text.Trim());

            }
            catch (System.Xml.XmlException)
            {
                Mbox.ShowSimpleMsgBoxError("Please use an NCName for the XPath ID (hint: no punctuation) ");
                return false;
            }
            return true;
        }
  
    } 

    class MyOpenedFile : OpenedFile
    {

        static Logger log = LogManager.GetLogger("OpenDoPE_Wed");

        List<IViewContent> registeredViews = new List<IViewContent>();

        public override IList<IViewContent> RegisteredViewContents
        {
            get { return registeredViews.AsReadOnly(); }
        }

        public override event EventHandler FileClosed;

        public override void RegisterView(IViewContent view)
        {
            registeredViews.Add(view);
            log.Info("Registered " + view.GetType().FullName);

            if (WorkbenchSingleton.Workbench != null)
            {
                WorkbenchSingleton.Workbench.ActiveViewContentChanged += WorkbenchActiveViewContentChanged;
                if (WorkbenchSingleton.Workbench.ActiveViewContent == view)
                {
                    SwitchedToView(view);
                }
            }
            
        }

        void WorkbenchActiveViewContentChanged(object sender, EventArgs e)
        {
            IViewContent newView = WorkbenchSingleton.Workbench.ActiveViewContent;

            if (!registeredViews.Contains(newView))
                return;

            SwitchedToView(newView);
        }


        public override void UnregisterView(IViewContent view)
        {
            registeredViews.Remove(view);
            if (registeredViews.Count > 0)
            {
                if (currentView == view)
                {
                    SaveCurrentView();
                    currentView = null;
                }
            }
            else
            {
                // all views to the file were closed
                CloseIfAllViewsClosed();
            }
        }


    }

}
